package cn.huaqingcheng.tool.core;

import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 日期时间工具
 *
 * @apiNote 特指 {@link java.time}
 */
@SuppressWarnings("unused")
public interface TimeTool {

    /**
     * 松散的日期格式 允许前后可选空格
     * <li>year: 年 任意位数数字的年, 允许负数</li>
     * <li>month: 月 1 ~ 2 位数字</li>
     * <li>day: 日 1 ~ 2 位数字</li>
     */
    Pattern LOOSE_DATE = Pattern.compile("\\s*?(?<year>-?\\d+)-(?<month>\\d{1,2})-(?<day>\\d{1,2})\\s*?");

    /**
     * 松散的时间格式 允许前后可选空格
     * <li> hour: 1 ~ 2 位纯数字, 小时 </li>
     * <li> minute: 1 ~ 2 位纯数字, 分钟 </li>
     * <li> second: 1 ~ 2 位纯数字, 秒 </li>
     * <li> ms: 1 ~ 4 位纯数字, 毫秒 (可选)</li>
     */
    Pattern LOOSE_TIME = Pattern.compile("\\s*?(?<hour>\\d{1,2}):(?<minute>\\d{1,2}):(?<second>\\d{1,2})(:(?<ms>\\d{1,4}))?\\s*?");

    /**
     * 松散的日期时间格式 允许前后可选空格, 中间为空格或 T (ISO格式)
     * <li>year: 年 任意位数数字的年, 允许负数</li>
     * <li>month: 月 1 ~ 2 位数字</li>
     * <li>day: 日 1 ~ 2 位数字</li>
     *
     * <li> hour: 1 ~ 2 位纯数字, 小时 </li>
     * <li> minute: 1 ~ 2 位纯数字, 分钟 </li>
     * <li> second: 1 ~ 2 位纯数字, 秒 </li>
     * <li> ms: 1 ~ 4 位纯数字, 毫秒 (可选)</li>
     */
    Pattern LOOSE_DATE_TIME = Pattern.compile(
        "\\s*?(?<year>-?\\d+)-(?<month>\\d{1,2})-(?<day>\\d{1,2})" +
        "[T\\s]" +
        "\\s*?(?<hour>\\d{1,2}):(?<minute>\\d{1,2}):(?<second>\\d{1,2})(:(?<ms>\\d{1,4}))?\\s*?"
    );

    /**
     * 松散的年月格式 允许前后可选空格
     */
    Pattern LOOSE_YEAR_MONTH = Pattern.compile("\\s*?(?<year>-?\\d+)-(?<month>\\d{1,2})\\s*?");

    /**
     * 松散的月日格式 允许前后可选空格
     */
    Pattern LOOSE_MONTH_DAY = Pattern.compile("\\s*?(?<month>\\d{1,2})-(?<day>\\d{1,2})\\s*?");

    String YEAR = "year";

    String MONTH = "month";

    String DAY = "day";

    String HOUR = "hour";

    String MINUTE = "minute";

    String SECOND = "second";

    String MS = "ms";

    /**
     * 尝试解析日期
     *
     * @param s 年月日字符串
     * @return 日期 Optional
     */
    @Contract(pure = true)
    static Optional<LocalDate> parseDateOpt(String s) {
        return Optional.ofNullable(parseDate(s));
    }

    /**
     * 解析日期
     * 如果入参为空, 返回 null; 适用于解析后仅传递, 并不使用
     *
     * @param data 年月日字符串
     * @return 可能为空的日期
     */
    @Nullable
    @Contract(pure = true)
    static LocalDate parseDate(String data) {
        if (StrTool.isEmpty(data)) {
            return null;
        }
        Matcher matcher = LOOSE_DATE.matcher(data);
        if (!matcher.matches()) {
            throw new DateTimeParseException("错误的日期格式, 应为: yyyy-MM-dd", data, -1);
        }

        return LocalDate.of(
            Integer.parseInt(matcher.group(YEAR)),
            Integer.parseInt(matcher.group(MONTH)),
            Integer.parseInt(matcher.group(DAY))
        );
    }

    /**
     * 尝试解析时间
     *
     * @param time 时分秒字符串
     * @return 时间 Optional
     */
    @Contract(pure = true)
    static Optional<LocalTime> parseTimeOpt(String time) {
        return Optional.ofNullable(parseTime(time));
    }

    /**
     * 解析时间
     * 如果入参为空, 返回 null; 适用于解析后仅传递, 并不使用
     *
     * @param time 时分秒字符串
     * @return 可能为空的时间
     */
    @Nullable
    @Contract(pure = true)
    static LocalTime parseTime(String time) {
        if (StrTool.isEmpty(time)) {
            return null;
        }
        Matcher matcher = LOOSE_TIME.matcher(time);
        if (!matcher.matches()) {
            throw new DateTimeParseException("错误的时间格式, 应为: HH:mm:ss", time, -1);
        }

        int hour = Integer.parseInt(matcher.group(HOUR));
        int minute = Integer.parseInt(matcher.group(MINUTE));
        int second = Integer.parseInt(matcher.group(SECOND));
        String ms = matcher.group(MS);

        return StrTool.isEmpty(ms)
               ? LocalTime.of(hour, minute, second)
               : LocalTime.of(hour, minute, second, Integer.parseInt(ms) * 1000);
    }

    /**
     * 尝试解析日期时间
     *
     * @param dateTime 日期时间字符串
     * @return 日期时间 Optional
     */
    @Contract(pure = true)
    static Optional<LocalDateTime> parseDateTimeOpt(String dateTime) {
        return Optional.ofNullable(parseDateTime(dateTime));
    }

    /**
     * 解析日期时间
     *
     * @param dateTime 日期时间字符串
     * @return 可能为空的日期时间
     */
    @Nullable
    @Contract(pure = true)
    static LocalDateTime parseDateTime(String dateTime) {
        if (StrTool.isEmpty(dateTime)) {
            return null;
        }
        Matcher matcher = LOOSE_TIME.matcher(dateTime);
        if (!matcher.matches()) {
            throw new DateTimeParseException("错误的时间格式, 应为: yyyy-MM-dd HH:mm:ss", dateTime, -1);
        }

        int year = Integer.parseInt(matcher.group(YEAR));
        int month = Integer.parseInt(matcher.group(MONTH));
        int day = Integer.parseInt(matcher.group(DAY));
        int hour = Integer.parseInt(matcher.group(HOUR));
        int min = Integer.parseInt(matcher.group(MINUTE));
        int s = Integer.parseInt(matcher.group(SECOND));
        String ms = matcher.group(MS);

        return StrTool.isEmpty(ms)
               ? LocalDateTime.of(year, month, day, hour, min, s)
               : LocalDateTime.of(year, month, day, hour, min, s, Integer.parseInt(ms) * 1000);
    }

    /**
     * 解析年月
     *
     * @param yearMonth 年月字符串
     */
    @Contract(pure = true)
    static Optional<YearMonth> parseYearMonthOpt(String yearMonth) {
        return Optional.ofNullable(parseYearMonth(yearMonth));
    }

    /**
     * 解析 年月
     *
     * @param yearMonth 年月字符串
     * @return 可能为空
     */
    @Nullable
    @Contract(pure = true)
    static YearMonth parseYearMonth(String yearMonth) {
        if (StrTool.isEmpty(yearMonth)) {
            return null;
        }
        Matcher matcher = LOOSE_YEAR_MONTH.matcher(yearMonth);
        if (!matcher.matches()) {
            throw new DateTimeParseException("错误的年月格式", yearMonth, -1);
        }
        return YearMonth.of(
            Integer.parseInt(matcher.group(YEAR)),
            Integer.parseInt(matcher.group(MONTH))
        );
    }

    /**
     * 解析月日
     *
     * @param monthDay 月日字符串
     */
    @Contract(pure = true)
    static Optional<MonthDay> parseMonthDayOpt(String monthDay) {
        return Optional.ofNullable(parseMonthDay(monthDay));
    }

    /**
     * 解析月日
     *
     * @param monthDay 月日字符串
     * @return 可能为空
     */
    @Nullable
    @Contract(pure = true)
    static MonthDay parseMonthDay(String monthDay) {
        if (StrTool.isEmpty(monthDay)) {
            return null;
        }
        Matcher matcher = LOOSE_MONTH_DAY.matcher(monthDay);
        if (!matcher.matches()) {
            throw new DateTimeParseException("错误的时间格式", monthDay, -1);
        }

        return MonthDay.of(
            Integer.parseInt(matcher.group(MONTH)),
            Integer.parseInt(matcher.group(DAY))
        );
    }

    /**
     * 格式化日期
     */
    @Contract(pure = true)
    static String format(LocalDate date) {
        return DateTimeFormatter.ISO_LOCAL_DATE.format(date);
    }

    /**
     * 格式化时间
     */
    @Contract(pure = true)
    static String format(LocalTime date) {
        return DateTimeFormatter.ISO_LOCAL_TIME.format(date);
    }

    /**
     * 格式化日期时间
     */
    @Contract(pure = true)
    static String format(LocalDateTime date) {
        return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(date);
    }

}
